#include "config.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include <adt.h>

#define DBG_SUBSYS S_LIBYLIB

#include "sysy_lib.h"
#include "ylog.h"
#include "dbg.h"
#include "fnotify.h"

/*
 * open the error log
 *
 * we have 3 possilities:
 * - stderr (default)
 * - syslog
 * - logfile
 */

ylog_t __ylog__[YLOG_TYPE_MAX];
ylog_t (*g_ylogs)[YLOG_TYPE_MAX] = NULL;


int syslog_init()
{
        setlogmask(LOG_UPTO(LOG_INFO));
        openlog("FusionStor", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL1);
        return 0;
}

int syslog_close()
{
        closelog();
        return 0;
}

static int __open(const char *file)
{
        return open(file, O_APPEND | O_CREAT | O_WRONLY, 0644);
}

int ylog_init(logmode_t logmode, char file[][MAX_PATH_LEN])
{
	int i,ret, fd;
	struct stat stbuf;
        ylog_t *ylog;

	if (g_ylogs == NULL) {
		for(i=0;i<YLOG_TYPE_MAX;i++){
                        ylog = &__ylog__[i];
			if (logmode == YLOG_FILE && file[i]) {
				ret = pthread_rwlock_init(&ylog->lock, NULL);
				if (unlikely(ret)) {
					fprintf(stderr, "ylog init rwlock %u", ret);
					goto err_ret;
				}

				ret = sy_spin_init(&ylog->spinlock);
				if (unlikely(ret)) {
					fprintf(stderr, "ylog init spinlock %u", ret);
					goto err_ret;
				}

				strcpy(ylog->file, file[i]);
				ylog->count = 0;
				ylog->time = 0;

				fd = __open(file[i]);
				if (fd == -1) {
					ret = errno;
					fprintf(stderr, "open(%s, ...) ret (%d) %s\n", file[i], ret,
							strerror(ret));
					goto err_ret;
				}
				ylog->logfd = fd;

				ret = fstat(ylog->logfd, &stbuf);
				if (ret) {
					goto  err_ret;
				}
				ylog->size = stbuf.st_size;
				ylog->isparent = 1;
			} else if (logmode == YLOG_STDERR) {
				ylog->logfd = 2;
				ylog->size = 0;
			}

			__ylog__[i].logmode = logmode;
		}
		g_ylogs = &__ylog__;
	}

	return 0;
err_ret:
	return ret;
}


int ylog_reset(logtype_t logtype)
{
        int ret, fd;
        struct stat stbuf;
        ylog_t *ylog;

        if (logtype >= YLOG_TYPE_MAX)
                return 0;

        ylog = &__ylog__[logtype];

        if (ylog->logmode == YLOG_STDERR) {
                return 0;
        }

        ret = pthread_rwlock_wrlock(&ylog->lock);
        if (ret) {
                YASSERT_NOLOG(0);
        }

        close(ylog->logfd);

        fd = __open(ylog->file);
        if (fd == -1) {
                YASSERT_NOLOG(0);
        }
        ylog->logfd = fd;

        ret = fstat(ylog->logfd, &stbuf);
        if (ret) {
                YASSERT_NOLOG(0);
        }
        ylog->size = stbuf.st_size;

        pthread_rwlock_unlock(&ylog->lock);

        return 0;
}

int ylog_destroy(logtype_t logtype)
{
        ylog_t *ylog;

        if (g_ylogs) {
                ylog = &__ylog__[logtype];
                if (ylog->logmode == YLOG_FILE && ylog->logfd != -1)
                        (void) sy_close(ylog->logfd);
        }

        return 0;
}

//check, and set size = 0, if need rollover.
static int __check_rollover_with_resize(logtype_t logtype, const char *_msg, int *rollover)
{
        int ret;
        ylog_t *ylog;

        *rollover = 0;

        ylog = &__ylog__[logtype];

        ret = sy_spin_lock(&ylog->spinlock);
        if (ret) {
                fprintf(stderr, "ylog write spinlock error %u", ret);
                goto err_ret;
        }

        if (ylog->size > gloconf.log_max_bytes) {
                *rollover = 1;
                ylog->size = 0;
        }

        ylog->size += strlen(_msg);
        sy_spin_unlock(&ylog->spinlock);

        return 0;
err_ret:
        return ret;
}

static int __do_rollover_nolock(logtype_t logtype)
{
        int fd, ret;
        char target_file[MAX_PATH_LEN], t[200];
        time_t now;
        struct tm *tm_now, tmt;
        ylog_t *ylog;

        ylog = &__ylog__[logtype];

        close(ylog->logfd);

        time(&now);
        tm_now = localtime_r(&now, &tmt);
        strftime(t, 200, "%Y%m%d-%H%M%S", tm_now);
        snprintf(target_file, MAX_PATH_LEN, "%s-%s", ylog->file, t);

        ret = rename(ylog->file, target_file);
        if (ret == -1) {
                ret = errno;
                if (ret == ENOENT) {
                        // ret = 0;
                } else {
                        goto err_ret;
                }
        }

        fd = __open(ylog->file);
        if (fd == -1) {
                ret = errno;
                goto err_ret;
        }

        ylog->logfd = fd;

        return 0;
err_ret:
        return ret;
}

int __ylog_write_rollover_rwlock(logtype_t logtype, const char *_msg)
{
        int ret;
        ylog_t *ylog;

        ylog = &__ylog__[logtype];

        ret = pthread_rwlock_wrlock(&ylog->lock);
        if (ret) {
                YASSERT_NOLOG(0);
        }

        ret = __do_rollover_nolock(logtype);
        if (ret) {
                YASSERT_NOLOG(0);
        }

        ret = write(ylog->logfd, _msg, strlen(_msg));
        if (ret < 0) {
                YASSERT_NOLOG(0);
        }

        pthread_rwlock_unlock(&ylog->lock);

        return 0;
}

int __ylog_write_rdlock(logtype_t logtype, const char *_msg)
{
        int ret;
        ylog_t *ylog;

        ylog = &__ylog__[logtype];

        ret = pthread_rwlock_rdlock(&ylog->lock);
        if (ret) {
                YASSERT_NOLOG(0);
        }

        ret = write(ylog->logfd, _msg, strlen(_msg));
        if (ret < 0) {
                // TODO errno: 28
                YASSERT_NOLOG(0);
        }

        pthread_rwlock_unlock(&ylog->lock);

        return 0;
}

int ylog_write(logtype_t logtype, const char *_msg)
{
        int ret, rollover;
        ylog_t *ylog;

        ylog = &__ylog__[logtype];

        if (g_ylogs && ylog->logmode == YLOG_FILE && ylog->logfd != -1) {
                if (ylog->isparent) {
                        ylog_reset(logtype);
                        ret = write(ylog->logfd, _msg, strlen(_msg));
                        if (ret < 0) {
                                YASSERT_NOLOG(0);
                        }
                } else {
                        ret = __check_rollover_with_resize(logtype, _msg, &rollover);
                        if (ret) {
                                YASSERT_NOLOG(0);
                        }

                        if (rollover) {
                                ret = __ylog_write_rollover_rwlock(logtype, _msg);
                                if (ret) {
                                        YASSERT_NOLOG(0);
                                }
                        } else {
                                ret = __ylog_write_rdlock(logtype, _msg);
                                if (ret) {
                                        YASSERT_NOLOG(0);
                                }
                        }
                }
        } else {
                fprintf(stderr, "%s", _msg);
        }

        return 0;
}

int ylog_set_parent(logtype_t logtype, int parent)
{
        __ylog__[logtype].isparent = parent;
        return 0;
}

int yroc_create(const char *filename, int *fd)
{
        DIR * rootdir;
        int fddir;
        int fdnew;
        int ret = -1;
        char status[] = "status", dir[MAX_PATH_LEN];

        if (!filename || !strlen(filename))
                GOTO(err_ret, errno);

        snprintf(dir, sizeof(dir), "%s/%s", YROC_ROOT, filename);

        ret = path_validate(dir, YLIB_ISDIR, YLIB_DIRCREATE);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        rootdir = opendir(dir);
        if (NULL == rootdir) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        fddir = dirfd(rootdir);
        if (-1 == fddir) {
                ret = errno;
                GOTO(close_fddir, ret);
        }

        fdnew = openat(fddir, status, O_CREAT | O_WRONLY, FILEMODE);
        if (-1 == fdnew) {
                ret = errno;
                GOTO(close_fdnew, ret);
        }

        *fd = fdnew;

        close(fddir);
        closedir(rootdir);
        return 0;
close_fdnew:
        close(fdnew);
close_fddir:
        close(fddir);
err_ret:
        return ret;
}

int yroc_write(int fd, const void * buffer, size_t count)
{
        int ret = -1;


        if (!count || !buffer)
                return 0;

        lseek(fd, 0 , SEEK_SET);

        while (count > 0) {
                ssize_t bytes_write;

                bytes_write = write(fd, buffer, count);
                if (-1 == bytes_write) {
                        if (EINTR == errno)
                                continue;
                        else
                                GOTO(err_ret, errno);
                }

                count -= bytes_write;
        }

        ret = 0;
err_ret:
        return ret;
}
